package com.disruptor.demo.handler;

import com.disruptor.demo.event.SeckillOrderEvent;
import com.lmax.disruptor.ExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * Disruptor全局异常处理器
 * 角色定位：Disruptor框架的"安全网"，负责捕获所有消费者处理器（如StockCheckHandler、OrderProcessHandler）未处理的异常
 * 核心价值：
 * 1. 避免单个事件处理失败导致整个Disruptor消费者线程终止（防止级联失败）
 * 2. 对失败事件提供重试机制，减少瞬时错误导致的业务损失
 * 3. 统一记录异常日志，便于问题排查与监控告警
 */
@Slf4j
@Component
public class SeckillOrderExceptionHandler implements ExceptionHandler<SeckillOrderEvent> {

    /**
     * 事件最大重试次数
     * 配置来源：从application.yml的seckill.max-retry注入（默认3次）
     * 设计目的：平衡重试效果与资源消耗（重试次数过多可能导致线程阻塞）
     */
    @Value("${seckill.max-retry}")
    private int maxRetry;

    /**
     * 处理事件消费过程中抛出的异常（核心方法）
     * 触发时机：当消费者处理器（如StockCheckHandler）的onEvent方法抛出未捕获异常时调用
     *
     * @param ex       事件处理过程中抛出的异常（如数据库连接异常、库存扣减冲突等）
     * @param sequence 异常事件在RingBuffer中的唯一序列号（用于定位事件位置）
     * @param event    发生异常的秒杀订单事件（包含订单ID、用户ID等关键信息）
     */
    @Override
    public void handleEventException(Throwable ex, long sequence, SeckillOrderEvent event) {
        String orderId = event.getOrderId(); // 提取订单ID，便于日志追踪

        // 1. 重试逻辑：未超过最大重试次数时，尝试重新处理事件
        if (event.getRetryCount() < maxRetry) {
            // 递增重试次数（事件对象复用，需显式更新）
            event.setRetryCount(event.getRetryCount() + 1);
            // 打印重试日志（包含重试次数和订单ID，便于追踪重试效果）
            log.error("【事件重试】订单处理异常，将进行第{}次重试 | orderId={} | 异常原因：{}",
                    event.getRetryCount(), orderId, ex.getMessage(), ex);

            // 生产环境补充逻辑：
            // - 将事件重新放入RingBuffer（通过Disruptor的publishEvent方法）
            // - 或放入专门的重试队列（如RabbitMQ延迟队列），避免阻塞主流程
        }
        // 2. 死信处理：超过最大重试次数，标记为死信（不再重试）
        else {
            log.error("【事件死信】订单处理失败，已达最大重试次数({}次)，进入死信流程 | orderId={}",
                    maxRetry, orderId, ex);

            // 生产环境补充逻辑：
            // - 写入死信表（MySQL）：记录订单信息、异常原因、重试次数，供人工排查
            // - 触发告警：通过钉钉/短信通知运维人员处理异常订单
            // - 避免死信堆积：定期清理或归档历史死信数据
        }
    }

    /**
     * 处理Disruptor启动过程中抛出的异常
     * 重要性：Disruptor启动失败会导致整个秒杀流程不可用，需重点监控
     *
     * @param ex 启动过程中抛出的异常（如RingBuffer初始化失败、线程池创建失败等）
     */
    @Override
    public void handleOnStartException(Throwable ex) {
        log.error("【Disruptor启动异常】服务启动失败，秒杀功能不可用 | 异常原因：{}", ex.getMessage(), ex);

        // 生产环境补充逻辑：
        // - 触发紧急告警：通过电话/短信通知核心开发人员
        // - 记录启动日志到独立文件：便于追溯启动失败根因（如配置错误、资源不足）
        // - 尝试自动重启：若为瞬时错误（如端口冲突），可触发服务重启逻辑
    }

    /**
     * 处理Disruptor关闭过程中抛出的异常
     * 场景：服务优雅关闭时，Disruptor释放资源（如线程池、RingBuffer）可能出现的异常
     *
     * @param ex 关闭过程中抛出的异常（如线程中断异常、资源释放失败等）
     */
    @Override
    public void handleOnShutdownException(Throwable ex) {
        log.error("【Disruptor关闭异常】服务关闭过程中发生错误 | 异常原因：{}", ex.getMessage(), ex);

        // 生产环境补充逻辑：
        // - 记录关闭日志：用于分析服务退出是否正常（如是否因OOM强制关闭）
        // - 强制释放资源：若优雅关闭失败，调用shutdownNow()强制终止线程池
    }
}
